
import fs from 'node:fs/promises'
import { existsSync } from 'node:fs'
import path from 'node:path'
import minimist from 'minimist'
import { brotliCompressSync, gzipSync } from 'node:zlib'
import pico from 'picocolors'
import { execa, execaSync } from 'execa'
import { cpus } from 'node:os'
import { createRequire } from 'node:module'
import { targets as allTargets, fuzzyMatchTarget } from './utils.js'
import { scanEnums } from './inline-enums.js'
import prettyBytes from 'pretty-bytes'

const require = createRequire(import.meta.url)
const args = minimist(process.argv.slice(2))
const targets = args._
const formats = args.formats || args.f
const devOnly = args.devOnly || args.d
const prodOnly = !devOnly && (args.prodOnly || args.p)
const buildTypes = args.withTypes || args.t
const sourceMap = args.sourcemap || args.s
const isRelease = args.release
/** @type {boolean | undefined} */
const buildAllMatching = args.all || args.a
const writeSize = args.size
const commit = execaSync('git', ['rev-parse', '--short=7', 'HEAD']).stdout

const sizeDir = path.resolve('temp/size')


run()

async function run() {
    if (writeSize) await fs.mkdir(sizeDir, { recursive: true })
    const removeCache = scanEnums()
    try {
      const resolvedTargets = targets.length
        ? fuzzyMatchTarget(targets, buildAllMatching)
        : allTargets
      await buildAll(resolvedTargets)
      await checkAllSizes(resolvedTargets)
      if (buildTypes) {
        await execa(
          'pnpm',
          [
            'run',
            'build-dts',
            ...(targets.length
              ? ['--environment', `TARGETS:${resolvedTargets.join(',')}`]
              : []),
          ],
          {
            stdio: 'inherit',
          },
        )
      }
    } finally {
      removeCache()
    // console.log('removeCache')
    }
  }
  
  /**
   * Builds all the targets in parallel.
   * @param {Array<string>} targets - An array of targets to build.
   * @returns {Promise<void>} - A promise representing the build process.
   */
  async function buildAll(targets) {
    await runParallel(cpus().length, targets, build)
  }
  async function runParallel(maxConcurrency, source, iteratorFn) {
    /**@type {Promise<void>[]} */
    const ret = []
    /**@type {Promise<void>[]} */
    const executing = []
    for (const item of source) {
      const p = Promise.resolve().then(() => iteratorFn(item))
      ret.push(p)
  
      if (maxConcurrency <= source.length) {
        const e = p.then(() => {
          executing.splice(executing.indexOf(e), 1)
        })
        executing.push(e)
        if (executing.length >= maxConcurrency) {
          await Promise.race(executing)
        }
      }
    }
    return Promise.all(ret)
  }
/**
 * Builds the target.建立目标  
 * @param {string} target - The target to build.
 * @returns {Promise<void>} - A promise representing the build process.
 */
async function build(target) {
    const pkgDir = path.resolve(`packages/${target}`)
    const pkg = require(`${pkgDir}/package.json`)
  
    // if this is a full build (no specific targets), ignore private packages
    if ((isRelease || !targets.length) && pkg.private) {
      return
    }
  
    // if building a specific format, do not remove dist.
    if (!formats && existsSync(`${pkgDir}/dist`)) {
      await fs.rm(`${pkgDir}/dist`, { recursive: true })
    }
  
    const env =
      (pkg.buildOptions && pkg.buildOptions.env) ||
      (devOnly ? 'development' : 'production')
    await execa(
      'rollup',
      [
        '-c',
        '--environment',
        [
          `COMMIT:${commit}`,
          `NODE_ENV:${env}`,
          `TARGET:${target}`,
          formats ? `FORMATS:${formats}` : ``,
          prodOnly ? `PROD_ONLY:true` : ``,
          sourceMap ? `SOURCE_MAP:true` : ``,
        ]
          .filter(Boolean)
          .join(','),
      ],
      { stdio: 'inherit' },
    )
  }
  /**
 * Checks the sizes of all targets.检查所有目标的大小  
 * @param {string[]} targets - The targets to check sizes for. 要检查大小的目标
 * @returns {Promise<void>}
 */
async function checkAllSizes(targets) {
    if (devOnly || (formats && !formats.includes('global'))) {
      return
    }
    console.log()
    for (const target of targets) {
      await checkSize(target)
    }
    console.log()
  }
  
  /**
   * Checks the size of a target. 检查目标的大小
   * @param {string} target - The target to check the size for. 要检查大小的目标
   * @returns {Promise<void>}
   */
  async function checkSize(target) {
    const pkgDir = path.resolve(`packages/${target}`)
    await checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
    if (!formats || formats.includes('global-runtime')) {
      await checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
    }
  }
  
  /**
   * Checks the file size.检查文件大小
   * @param {string} filePath - The path of the file to check the size for. 要检查大小的文件的路径 
   * @returns {Promise<void>}
   */
  async function checkFileSize(filePath) {
    if (!existsSync(filePath)) {
      return
    }
    const file = await fs.readFile(filePath)
    const fileName = path.basename(filePath)
  
    const gzipped = gzipSync(file)
    const brotli = brotliCompressSync(file)
  
    console.log(
      `${pico.gray(pico.bold(fileName))} min:${prettyBytes(
        file.length,
      )} / gzip:${prettyBytes(gzipped.length)} / brotli:${prettyBytes(
        brotli.length,
      )}`,
    )
  
    if (writeSize)
      await fs.writeFile(
        path.resolve(sizeDir, `${fileName}.json`),
        JSON.stringify({
          file: fileName,
          size: file.length,
          gzip: gzipped.length,
          brotli: brotli.length,
        }),
        'utf-8',
      )
  }